package com.tsd.utils;

import com.tsd.common.dao.CommonFileMapper;
import com.tsd.common.entity.CommonFile;
import com.tsd.common.entity.CommonFileExt;
import com.tsd.common.service.CommonFileService;
import com.tsd.core.datasource.SpringContextUtil;
import com.tsd.core.handler.NonStaticResourceHttpRequestHandler;
import com.tsd.core.utils.ContentType;
import com.tsd.core.utils.FileUtil;
import com.tsd.core.utils.HlpUtils;
import com.tsd.core.utils.ResourcesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 下载工具类
 */
public class DownloadUtil {
    //包长，默认是1MB
    private static final long PACKAGE_LEN = 1024 * 1024;
    private static final Logger LOGGER = LoggerFactory.getLogger("DownloadUtil");

    public static void procDownloadFile_old(CommonFileService baseFileInfoService, String sid, HttpServletRequest request, HttpServletResponse response) {
        FileInputStream inputStream = null;
        ServletOutputStream outputStream = null;
        try {
            CommonFile fileInfo = baseFileInfoService.getFileInfoBySid(sid);
            if (fileInfo == null) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                return;
            }
            String fileType = FileUtil.getFileType(fileInfo.getOriginal_file_name());
            if (HlpUtils.fileExists(fileInfo.getFile_path())) {
                File file = new File(fileInfo.getFile_path());
                if (RequestMethod.HEAD.name().equalsIgnoreCase(request.getMethod())) {
                    response.setHeader("Content-Length", "" + file.length());
                    response.addHeader("Access-Control-Expose-Headers", "Package-Len");
                    response.setHeader("Package-Len", "" + PACKAGE_LEN);
                    return;
                }
                inputStream = new FileInputStream(file);
                String contentType = ContentType.getContentType(fileType);
                String Connection = request.getHeader("Connection");
                if (!HlpUtils.isEmpty(Connection)) {
                    response.setHeader("Connection", Connection);
                }
                String Range = request.getHeader("Range");
                long start = 0;
                long end = file.length() - 1;
                long length = file.length();
                if (!HlpUtils.isEmpty(Range) && Range.startsWith("bytes=")) {
                    //断点续传
                    response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
//                    Range: bytes=57344-360447
                    String[] bytes = Range.substring("bytes=".length()).split("-");
                    start = Long.parseLong(bytes[0]);
                    if (bytes.length > 1) {
                        end = Long.parseLong(bytes[1]);
                        if (end >= file.length() - 1) {
                            response.setStatus(HttpServletResponse.SC_OK);
                        }
                    }
                    length = end - start + 1;
                } else {
                    //一次性传输或者第一次请求
                    if (length > PACKAGE_LEN) {
                        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
                    } else {
                        response.setStatus(HttpServletResponse.SC_OK);
                    }
                }
                if (length > PACKAGE_LEN) {
                    length = PACKAGE_LEN;
                    end = start + length;
                }
                response.setHeader("Content-Type", contentType);
                response.setHeader("Content-Disposition", "inline; filename=" + URLEncoder.encode(fileInfo.getOriginal_file_name(), "utf-8"));
//                response.setHeader("expires","14400000");
                response.setHeader("Pragma", "No-cache");
                response.setHeader("Cache-Control", "no-cache");
                if (file.length() > length) {
                    response.setHeader("Accept-Ranges", "bytes");
//                    response.setHeader("Transfer-Encoding", "chunked");
                    response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + file.length());
                    response.setHeader("Content-Length", "" + (end - start + 1));
                } else {
                    response.setHeader("Content-Length", "" + file.length());
                }
//                response.setHeader("Last-Modified",""+(new Date(file.lastModified())));
//                response.setHeader("Server","Ysh File API");
//                response.setHeader("name", "attachment");
                outputStream = response.getOutputStream();
                if (start > 0) {
                    inputStream.skip(start);
                }
                byte[] buffer = new byte[10240];
                int bufferLength = buffer.length;
                int bytesToRead = bufferLength;
                if (length > 0L && length < (long) bufferLength) {
                    bytesToRead = (int) length;
                }

                long totalRead = 0L;
                int read;
                while (bytesToRead > 0 && -1 != (read = inputStream.read(buffer, 0, bytesToRead))) {
                    outputStream.write(buffer, 0, read);
                    totalRead += read;
                    if (length > 0L) {
                        bytesToRead = (int) Math.min(length - totalRead, bufferLength);
                    }
                }
                outputStream.flush();
//                IOUtils.copyLarge(inputStream,outputStream , start, length);
//                response.flushBuffer();
            } else {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try {
                response.getOutputStream().write(("读取文件出错:" + e.getMessage()).getBytes(StandardCharsets.UTF_8));
            } catch (Exception ee) {
                ee.printStackTrace();
            }
        } finally {
            safeClose(inputStream);
            safeClose(outputStream);
        }
    }

    /**
     * 处理文件下载，根据文件sid获取文件路径，如果文件信息或者文件路径不存在，则报404，否则进行分片和断点下载
     *
     * @param baseFileInfoService 通用文件服务对象
     * @param sid                 文件sid
     * @param request             http请求对象
     * @param response            http响应对象
     */
    public static void procDownloadFile(CommonFileService baseFileInfoService, String sid, HttpServletRequest request, HttpServletResponse response) {
        try {
            CommonFile fileInfo = baseFileInfoService.getFileInfoBySid(sid);
            if (fileInfo == null) {
                response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            } else {
                if (HlpUtils.fileExists(fileInfo.getFile_path())) {
                    fileChunkDownload(fileInfo, request, response);
                } else {
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try {
                response.getOutputStream().write(("读取文件出错:" + e.getMessage()).getBytes(StandardCharsets.UTF_8));
            } catch (Exception ee) {
                ee.printStackTrace();
            }
        }
    }

    /**
     * 处理文件下载，根据文件sid获取文件路径，如果文件信息或者文件路径不存在，则报404，否则进行分片和断点下载
     *
     * @param sid      文件sid
     * @param request  http请求对象
     * @param response http响应对象
     */
    public static void procDownloadExcelTemplateFile(String sid, HttpServletRequest request, HttpServletResponse response) {
        try {
            CommonFileMapper commonFileMapper = SpringContextUtil.getBean(CommonFileMapper.class);
            CommonFileExt fileInfo = commonFileMapper.selectExcelTemplateByBizSid(sid);
            if (fileInfo == null) {
                String templatePath = ResourcesUtil.getExcelTemplatePath(sid);
                if (!HlpUtils.isEmpty(templatePath) && FileUtil.isFileExist(templatePath)) {
                    fileInfo = new CommonFileExt();
                    fileInfo.setFile_path(templatePath);
                    fileInfo.setOriginal_file_name(sid);
                    fileChunkDownload(fileInfo, request, response);
                } else {
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                }
            } else {
                if (HlpUtils.fileExists(fileInfo.getFile_path())) {
                    fileChunkDownload(fileInfo, request, response);
                } else {
                    response.setStatus(HttpServletResponse.SC_NOT_FOUND);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            try {
                response.getOutputStream().write(("读取文件出错:" + e.getMessage()).getBytes(StandardCharsets.UTF_8));
            } catch (Exception ee) {
                ee.printStackTrace();
            }
        }
    }

    /**
     * 文件支持分块下载和断点续传
     *
     * @param fileInfo 文件信息
     * @param request  请求
     * @param response 响应
     */
    static private void fileChunkDownload(CommonFile fileInfo, HttpServletRequest request, HttpServletResponse response) {
        String filePath = fileInfo.getFile_path();
        String range = request.getHeader("Range");
//        LOGGER.info("current request rang:" + range);
        File file = new File(filePath);
        //开始下载位置
        long startByte = 0;
        //结束下载位置
        long endByte = file.length() - 1;
//        LOGGER.info("文件开始位置：{}，文件结束位置：{}，文件总长度：{}", startByte, endByte, file.length());
        //有range的话
        if (range != null && range.contains("bytes=") && range.contains("-")) {
            range = range.substring(range.lastIndexOf("=") + 1).trim();
            String[] ranges = range.split("-");
            try {
                //判断range的类型
                if (ranges.length == 1) {
                    //类型一：bytes=-2343
                    if (range.startsWith("-")) {
                        endByte = Long.parseLong(ranges[0]);
                    }
                    //类型二：bytes=2343-
                    else if (range.endsWith("-")) {
                        startByte = Long.parseLong(ranges[0]);
                    }
                }
                //类型三：bytes=22-2343
                else if (ranges.length == 2) {
                    startByte = Long.parseLong(ranges[0]);
                    endByte = Long.parseLong(ranges[1]);
                }

            } catch (NumberFormatException e) {
                startByte = 0;
                endByte = file.length() - 1;
                LOGGER.error("Range Occur Error,Message:{}", e.getLocalizedMessage());
            }
        }
        //要下载的长度
        long contentLength = endByte - startByte + 1;
        //文件名
        String fileName = fileInfo.getOriginal_file_name();
        //文件类型
        String contentType = request.getServletContext().getMimeType(fileName);
        byte[] fileNameBytes = fileName.getBytes(StandardCharsets.UTF_8);
        try {
            fileName = URLEncoder.encode(fileInfo.getOriginal_file_name(), "utf-8");// new String(fileNameBytes, 0, fileNameBytes.length, StandardCharsets.ISO_8859_1);
        } catch (Exception ignored) {
        }
        //各种响应头设置
        //支持断点续传，获取部分字节内容：
        response.setHeader("Accept-Ranges", "bytes");
        //http状态码要为206：表示获取部分内容
        response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
        response.setContentType(contentType);
        response.setHeader("Content-Type", contentType);
        //inline表示浏览器直接使用，attachment表示下载，fileName表示下载的文件名
        response.setHeader("Content-Disposition", "inline;filename=" + fileName);
        response.setHeader("Content-Length", String.valueOf(contentLength));
        // Content-Range，格式为：[要下载的开始位置]-[结束位置]/[文件总大小]
        response.setHeader("Content-Range", "bytes " + startByte + "-" + endByte + "/" + file.length());

        BufferedOutputStream outputStream = null;
        RandomAccessFile randomAccessFile = null;
        //已传送数据大小
        long transmitted = 0;
        try {
            randomAccessFile = new RandomAccessFile(file, "r");
            outputStream = new BufferedOutputStream(response.getOutputStream());
            byte[] buff = new byte[40960];
            int len = 0;
            randomAccessFile.seek(startByte);
            //warning：判断是否到了最后不足4096（buff的length）个byte这个逻辑（(transmitted + len) <= contentLength）要放前面
            //不然会会先读取randomAccessFile，造成后面读取位置出错;
            while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buff)) != -1) {
                outputStream.write(buff, 0, len);
                transmitted += len;
            }
            //处理不足buff.length部分
            if (transmitted < contentLength) {
                len = randomAccessFile.read(buff, 0, (int) (contentLength - transmitted));
                outputStream.write(buff, 0, len);
                transmitted += len;
            }

            outputStream.flush();
            response.flushBuffer();
//            LOGGER.info("下载完毕：" + startByte + "-" + endByte + "：" + transmitted);
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.error("用户下载IO异常，Message：{}", e.getLocalizedMessage());
        } finally {
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }///end try
    }

    static private void safeClose(InputStream in) {
        if (in != null) {
            try {
                in.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    static private void safeClose(ServletOutputStream outputStream) {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 调用Spring的NonStaticResourceHttpRequestHandler下载文件
     *
     * @param fileInfoService
     * @param sid
     * @param nonStaticResourceHttpRequestHandler
     * @param request
     * @param response
     * @throws Exception
     */
    public static void downloadStatic(CommonFileService fileInfoService, String sid, NonStaticResourceHttpRequestHandler nonStaticResourceHttpRequestHandler, HttpServletRequest request, HttpServletResponse response) throws Exception {

        CommonFile fileInfo = fileInfoService.getFileInfoBySid(sid);
        File filePath = new File(fileInfo.getFile_path());
        Path path = Paths.get(fileInfo.getFile_path());
        if (filePath.exists()) {
            String fileType = FileUtil.getFileType(fileInfo.getOriginal_file_name());
            String contentType = ContentType.getContentType(fileType);
            response.setContentType(contentType);
            request.setAttribute(NonStaticResourceHttpRequestHandler.ATTR_FILE, path);
            nonStaticResourceHttpRequestHandler.handleRequest(request, response);
        } else {
            response.setStatus(HttpServletResponse.SC_NOT_FOUND);
            response.setCharacterEncoding(StandardCharsets.UTF_8.toString());
        }
    }

}
